﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace Oliver.Controls
{
    public class PerspectivePanel : Panel
    {
        public PerspectivePanel()
        {
            #region Trick for Keyevents, Panel is not focusable, but contentcontrol has it
            _contentControl = new ContentControl();
            _contentControl.IsTabStop = true;
            _contentControl.KeyDown += new KeyEventHandler(_contentControl_KeyDown);
            this.Children.Add(_contentControl);
            _contentControl.Focus();

            #endregion

            #region MouseWheel

            this.MouseWheel += new MouseWheelEventHandler(PerspectivePanel_MouseWheel);

            #endregion
        }

        void PerspectivePanel_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            e.Handled = true;
            if (e.Delta > 0)
            {
                Move(+1);
            }
            else
            {
                Move(-1);
            }
        }

        ContentControl _contentControl;
        PerspectivePosition[] _pp = null;
        int _currentPosition;


        #region Keyboard

        void _contentControl_KeyDown(object sender, KeyEventArgs e)
        {
            e.Handled = true;
            DoKey(e.Key);
        }

        private void DoKey(System.Windows.Input.Key key)
        {
            switch(key)
            {
                case System.Windows.Input.Key.Left:
                    Move(-1);
                    break;

                case System.Windows.Input.Key.Right:
                    Move(+1);
                    break;

                default:
                    break;
            }
        }

        #endregion

        #region Overrides

        // Position of Children
        protected override Size ArrangeOverride(Size finalSize)
        {
            List<PerspectivePosition> result = new List<PerspectivePosition>();

            // Zero Position
            PerspectivePosition pNull = new PerspectivePosition();
            pNull.Opacity = 0;
            pNull.ScaleFactor = 2d;
            pNull.Left = 0;
            pNull.Top = 0;
            result.Add(pNull);

            // Normal Positions
            for (int i = 0; i < this.Children.Count; i++)
            {
                if (this.Children[i] is ContentControl)
                {
                    continue;
                }

                // Create new position
                PerspectivePosition position = GetPosition(i);
                result.Add(position);

                FrameworkElement fe = this.Children[i] as FrameworkElement;
                if (fe != null)
                {
                    // Mouse-Click 
                    fe.MouseLeftButtonDown -= element_MouseLeftButtonDown;
                    fe.MouseLeftButtonDown += new MouseButtonEventHandler(element_MouseLeftButtonDown);

                    // Opacity
                    fe.Opacity = position.Opacity;

                    // Z-Index
                    fe.SetValue(Canvas.ZIndexProperty, position.ZIndex);

                    // Position
                    Rect rect = new Rect(0, 0, _itemWidth, _itemHeight);
                    fe.Arrange(rect);

                    CreateTransformGroup(fe);
                    Animate(fe, position);
                }
            }

            _pp = result.ToArray();

            return base.ArrangeOverride(finalSize);
        }

        // Size of Control and Children
        protected override Size MeasureOverride(Size availableSize)
        {
            foreach (UIElement child in this.Children)
            {
                child.Measure(new Size(ItemWidth, ItemHeight));
            }
            return base.MeasureOverride(availableSize);
        }

        #endregion

        #region Move Logic

        void element_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Move(+1);
        }
        
        private void Move(int steps)
        {
            _currentPosition += steps;

            if (_currentPosition < 0)
            {
                _currentPosition = 0;
                return;
            }

            if (_currentPosition >= this.Children.Count - 1)
            {
                _currentPosition = this.Children.Count;
                return;
            }

            for (int i = 0; i < this.Children.Count; i++)
            {
                FrameworkElement fe = this.Children[i] as FrameworkElement;
                
                int newPosition = i - _currentPosition;

                if (newPosition <= 0)
                {
                    // Hide Elements
                    Animate(fe, _pp[0]);
                }
                else
                {
                    if (_pp.Length > newPosition)
                    {
                        // Show position
                        Animate(fe, _pp[newPosition]);
                    }
                }
            }
        }

        #endregion

        #region Properties

        private double _itemWidth = 320;
        [Category(Defaults.CategoryName)]
        public double ItemWidth
        {
            get { return _itemWidth; }
            set { _itemWidth = value; InvalidateArrange(); }
        }
        
        private double _itemHeight = 240;
        [Category(Defaults.CategoryName)]
        public double ItemHeight
        {
            get { return _itemHeight; }
            set { _itemHeight = value; InvalidateArrange(); }
        }

        private double _offsetX = 30;
        [Category(Defaults.CategoryName)]
        public double OffsetX
        {
            get { return _offsetX; }
            set { _offsetX = value; InvalidateArrange(); }
        }

        private double _offsetY = 20;
        [Category(Defaults.CategoryName)]
        public double OffsetY
        {
            get { return _offsetY; }
            set { _offsetY = value; InvalidateArrange(); }
        }

        private double _offsetOpacity = .1;
        [Category(Defaults.CategoryName)]
        public double OffsetOpacity
        {
            get { return _offsetOpacity; }
            set { _offsetOpacity = value; InvalidateArrange(); }
        }

        private int _visibleItems = 0;

        [Category(Defaults.CategoryName)]
        public int VisibleItems
        {
            get { return _visibleItems; }
            set { _visibleItems = value; InvalidateArrange(); }
        }

        private int _milliSeconds = 250;
        [Category(Defaults.CategoryName)]
        public int MilliSecondsMove
        {
            get { return _milliSeconds; }
            set { _milliSeconds = value; }
        }

        #endregion

        #region Animation Methods

        private void CreateTransformGroup(UIElement element)
        {
            TransformGroup tg = null;

            if (element.RenderTransform == null
                || !(element.RenderTransform is TransformGroup))
            {
                tg = new TransformGroup();
                element.RenderTransform = tg;
            }
            else
            {
                tg = element.RenderTransform as TransformGroup;
            }

            // Translate Transform
            if (tg.Children.OfType<TranslateTransform>().Count() == 0)
            {
                TranslateTransform tt = new TranslateTransform();
                tg.Children.Add(tt);
            }

            // Scale Transform
            if (tg.Children.OfType<ScaleTransform>().Count() == 0)
            {
                ScaleTransform st = new ScaleTransform();
                tg.Children.Add(st);
            }
        }

        int _ressourceCounter = 0;
        Dictionary<UIElement, string> _resourceHelper = new Dictionary<UIElement, string>();

        private void Animate(UIElement element, PerspectivePosition position)
        {
            if (element ==  _contentControl)
            {
                return;
            }

            TimeSpan moveTime = new TimeSpan(0, 0, 0, 0, _milliSeconds);
            Duration duration = new Duration(moveTime);

            Storyboard sb = new Storyboard();

            if (!_resourceHelper.ContainsKey(element))
            {
                // New Storyboard
                string resourceKey = "Res_" + _ressourceCounter.ToString();
                this.Resources.Add(resourceKey, sb);
                _resourceHelper.Add(element, resourceKey);
                _ressourceCounter++;
            }
            else
            {
                // Replace existing Storyboard and save Resources
                string resourceKey = _resourceHelper[element];
                this.Resources.Remove(resourceKey);
                this.Resources.Add(resourceKey, sb);
            }

            // Translate X
            DoubleAnimation daTranslateTransformX = new DoubleAnimation();
            daTranslateTransformX.Duration = duration;
            daTranslateTransformX.To = position.Left;
            sb.Children.Add(daTranslateTransformX);
            Storyboard.SetTarget(daTranslateTransformX, element);
            Storyboard.SetTargetProperty(daTranslateTransformX
                , new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[0].(TranslateTransform.X)"));

            // Translate Y
            DoubleAnimation daTranslateTransformY = new DoubleAnimation();
            daTranslateTransformY.Duration = duration;
            daTranslateTransformY.To = position.Top;
            
            sb.Children.Add(daTranslateTransformY);
            Storyboard.SetTarget(daTranslateTransformY, element);
            Storyboard.SetTargetProperty(daTranslateTransformY
                , new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[0].(TranslateTransform.Y)"));

            // Scale X
            DoubleAnimation daScaleTransformX = new DoubleAnimation();
            daScaleTransformX.Duration = duration;
            daScaleTransformX.To = position.ScaleFactor;
            sb.Children.Add(daScaleTransformX);
            Storyboard.SetTarget(daScaleTransformX, element);
            Storyboard.SetTargetProperty(daScaleTransformX
                , new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[1].(ScaleTransform.ScaleX)"));

            // Scale Y
            DoubleAnimation daScaleTransformY = new DoubleAnimation();
            daScaleTransformY.Duration = duration;
            daScaleTransformY.To = position.ScaleFactor;
            sb.Children.Add(daScaleTransformY);
            Storyboard.SetTarget(daScaleTransformY, element);
            Storyboard.SetTargetProperty(daScaleTransformY
                , new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[1].(ScaleTransform.ScaleY)"));
            
            // Opacity
            DoubleAnimation daOpacity = new DoubleAnimation();
            daOpacity.Duration = duration;
            daOpacity.To = position.Opacity;
            sb.Children.Add(daOpacity);
            Storyboard.SetTarget(daOpacity, element);
            Storyboard.SetTargetProperty(daOpacity
                , new PropertyPath("(UIElement.Opacity)"));

            sb.Begin();
        }

        

        #endregion

        #region Define Positions

        private PerspectivePosition GetPosition(int position)
        {
            PerspectivePosition p = new PerspectivePosition();

            double relation;
            if (_visibleItems == 0)
            {
                relation = position / Convert.ToDouble(this.Children.Count-1);
            }
            else
            {
                relation = position / Convert.ToDouble(_visibleItems);
            }

            if (relation < 0)
            {
                relation = 0;
            }

            if (relation > 1)
            {
                relation = 1;
            }

            // Size
            p.Width = ItemWidth;
            p.Height = ItemHeight;

            // scale
            p.ScaleFactor = 1 - relation;

            // size
            p.Left = position * _offsetX;
            p.Top = ItemHeight - (p.Height * p.ScaleFactor) + position * _offsetY;

            // Opacity
            p.Opacity = 1.0 - _offsetOpacity * relation;

            // Z-
            p.ZIndex = -position;

            return p;
        }

        #endregion
    }

    #region Helper Class

    internal class PerspectivePosition
    {
        public double Left { get; set; }
        public double Top { get; set; }
        public double Width { get; set; }
        public double Height { get; set; }
        public double Opacity { get; set; }
        public double ScaleFactor { get; set; }
        public int ZIndex { get; set; }
    }

    #endregion
}
